Вичерпний посібник з API компілятора TypeScript, що охоплює абстрактні синтаксичні дерева (AST), аналіз, трансформацію та генерацію коду для розробників.
API компілятора TypeScript: Майстерність маніпуляції AST та трансформації коду
API компілятора TypeScript надає потужний інтерфейс для аналізу, маніпулювання та генерації коду TypeScript та JavaScript. В його основі лежить абстрактне синтаксичне дерево (AST) — структуроване представлення вашого вихідного коду. Розуміння того, як працювати з AST, відкриває можливості для створення передових інструментів, таких як лінтери, форматувальники коду, статичні аналізатори та власні генератори коду.
Що таке API компілятора TypeScript?
API компілятора TypeScript — це набір інтерфейсів та функцій TypeScript, які розкривають внутрішню роботу компілятора TypeScript. Він дозволяє розробникам програмно взаємодіяти з процесом компіляції, виходячи за рамки простої компіляції коду. Ви можете використовувати його для:
- Аналіз коду: Перевіряти структуру коду, виявляти потенційні проблеми та витягувати семантичну інформацію.
- Трансформація коду: Модифікувати існуючий код, додавати нові функції або виконувати рефакторинг коду автоматично.
- Генерація коду: Створювати новий код з нуля на основі шаблонів або інших вхідних даних.
Цей API є важливим для створення складних інструментів розробки, які покращують якість коду, автоматизують рутинні завдання та підвищують продуктивність розробників.
Розуміння абстрактного синтаксичного дерева (AST)
AST — це деревоподібне представлення структури вашого коду. Кожен вузол у дереві представляє синтаксичну конструкцію, таку як оголошення змінної, виклик функції або оператор керування потоком. API компілятора TypeScript надає інструменти для обходу AST, перевірки його вузлів та їх модифікації.
Розглянемо цей простий код TypeScript:
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));
AST для цього коду представлятиме оголошення функції, оператор повернення, шаблонний літерал, виклик console.log та інші елементи коду. Візуалізувати AST може бути складно, але інструменти, такі як AST explorer (astexplorer.net), можуть допомогти. Ці інструменти дозволяють вводити код і бачити відповідне йому AST у зручному для користувача форматі. Використання AST Explorer допоможе вам зрозуміти тип структури коду, якою ви будете маніпулювати.
Ключові типи вузлів AST
API компілятора TypeScript визначає різні типи вузлів AST, кожен з яких представляє різну синтаксичну конструкцію. Ось деякі поширені типи вузлів:
- SourceFile: Представляє цілий файл TypeScript.
- FunctionDeclaration: Представляє визначення функції.
- VariableDeclaration: Представляє оголошення змінної.
- Identifier: Представляє ідентифікатор (наприклад, ім'я змінної, ім'я функції).
- StringLiteral: Представляє рядковий літерал.
- CallExpression: Представляє виклик функції.
- ReturnStatement: Представляє оператор повернення.
Кожен тип вузла має властивості, які надають інформацію про відповідний елемент коду. Наприклад, вузол `FunctionDeclaration` може мати властивості для свого імені, параметрів, типу повернення та тіла.
Початок роботи з API компілятора
Щоб почати використовувати API компілятора, вам потрібно встановити TypeScript і мати базове розуміння синтаксису TypeScript. Ось простий приклад, який демонструє, як прочитати файл TypeScript і вивести його AST:
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015, // Цільова версія ECMAScript
true // SetParentNodes: true, щоб зберегти посилання на батьківські вузли в AST
);
function printAST(node: ts.Node, indent = 0) {
const indentStr = " ".repeat(indent);
console.log(`${indentStr}${ts.SyntaxKind[node.kind]}`);
node.forEachChild(child => printAST(child, indent + 1));
}
printAST(sourceFile);
Пояснення:
- Імпорт модулів: Імпортує модуль `typescript` та модуль `fs` для операцій з файловою системою.
- Читання вихідного файлу: Читає вміст файлу TypeScript з назвою `example.ts`. Вам потрібно буде створити файл `example.ts`, щоб це працювало.
- Створення SourceFile: Створює об'єкт `SourceFile`, який представляє корінь AST. Функція `ts.createSourceFile` розбирає вихідний код і генерує AST.
- Виведення AST: Визначає рекурсивну функцію `printAST`, яка обходить AST і виводить тип кожного вузла.
- Виклик printAST: Викликає `printAST`, щоб почати виведення AST з кореневого вузла `SourceFile`.
Щоб запустити цей код, збережіть його як файл `.ts` (наприклад, `ast-example.ts`), створіть файл `example.ts` з деяким кодом TypeScript, а потім скомпілюйте та запустіть код:
tsc ast-example.ts
node ast-example.js
Це виведе AST вашого файлу `example.ts` у консоль. Вивід покаже ієрархію вузлів та їх типи. Наприклад, він може показати `FunctionDeclaration`, `Identifier`, `Block` та інші типи вузлів.
Обхід AST
API компілятора надає кілька способів обходу AST. Найпростіший — це використання методу `forEachChild`, як показано в попередньому прикладі. Цей метод відвідує кожен дочірній вузол заданого вузла.
Для складніших сценаріїв обходу можна використовувати патерн `Visitor`. Відвідувач — це об'єкт, який визначає методи, що викликаються для конкретних типів вузлів. Це дозволяє налаштовувати процес обходу та виконувати дії залежно від типу вузла.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
class IdentifierVisitor {
visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
console.log(`Found identifier: ${node.text}`);
}
ts.forEachChild(node, n => this.visit(n));
}
}
const visitor = new IdentifierVisitor();
visitor.visit(sourceFile);
Пояснення:
- Клас IdentifierVisitor: Визначає клас `IdentifierVisitor` з методом `visit`.
- Метод Visit: Метод `visit` перевіряє, чи є поточний вузол `Identifier`. Якщо так, він виводить текст ідентифікатора. Потім він рекурсивно викликає `ts.forEachChild`, щоб відвідати дочірні вузли.
- Створення відвідувача: Створює екземпляр `IdentifierVisitor`.
- Початок обходу: Викликає метод `visit` для `SourceFile`, щоб розпочати обхід.
Цей приклад демонструє, як знайти всі ідентифікатори в AST. Ви можете адаптувати цей патерн для пошуку інших типів вузлів та виконання різних дій.
Трансформація AST
Справжня сила API компілятора полягає в його здатності трансформувати AST. Ви можете змінювати AST, щоб змінити структуру та поведінку вашого коду. Це основа для інструментів рефакторингу коду, генераторів коду та інших передових інструментів.
Щоб трансформувати AST, вам потрібно використовувати функцію `ts.transform`. Ця функція приймає `SourceFile` та список функцій `TransformerFactory`. `TransformerFactory` — це функція, яка приймає `TransformationContext` і повертає функцію `Transformer`. Функція `Transformer` відповідає за відвідування та трансформацію вузлів в AST.
Ось простий приклад, який демонструє, як додати коментар на початок файлу TypeScript:
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
const transformerFactory: ts.TransformerFactory = context => {
return transformer => {
return node => {
if (ts.isSourceFile(node)) {
// Створюємо початковий коментар
const comment = ts.addSyntheticLeadingComment(
node,
ts.SyntaxKind.MultiLineCommentTrivia,
" This file was automatically transformed ",
true // має новий рядок в кінці
);
return node;
}
return node;
};
};
};
const { transformed } = ts.transform(sourceFile, [transformerFactory]);
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed
});
const result = printer.printFile(transformed[0]);
fs.writeFileSync("example.transformed.ts", result);
Пояснення:
- TransformerFactory: Визначає функцію `TransformerFactory`, яка повертає функцію `Transformer`.
- Transformer: Функція `Transformer` перевіряє, чи є поточний вузол `SourceFile`. Якщо так, вона додає до вузла початковий коментар за допомогою `ts.addSyntheticLeadingComment`.
- ts.transform: Викликає `ts.transform` для застосування трансформації до `SourceFile`.
- Printer: Створює об'єкт `Printer` для генерації коду з трансформованого AST.
- Друк та запис: Друкує трансформований код і записує його в новий файл з назвою `example.transformed.ts`.
Цей приклад демонструє просту трансформацію, але ви можете використовувати той самий патерн для виконання складніших трансформацій, таких як рефакторинг коду, додавання операторів логування або генерація документації.
Передові техніки трансформації
Ось деякі передові техніки трансформації, які ви можете використовувати з API компілятора:
- Створення нових вузлів: Використовуйте функції `ts.createXXX` для створення нових вузлів AST. Наприклад, `ts.createVariableDeclaration` створює новий вузол оголошення змінної.
- Заміна вузлів: Замінюйте існуючі вузли новими за допомогою функції `ts.visitEachChild`.
- Додавання вузлів: Додавайте нові вузли до AST за допомогою функцій `ts.updateXXX`. Наприклад, `ts.updateBlock` оновлює блочний оператор новими операторами.
- Видалення вузлів: Видаляйте вузли з AST, повертаючи `undefined` з функції-трансформера.
Генерація коду
Після трансформації AST вам потрібно буде згенерувати з нього код. Для цього API компілятора надає об'єкт `Printer`. `Printer` приймає AST і генерує рядкове представлення коду.
Функція `ts.createPrinter` створює об'єкт `Printer`. Ви можете налаштувати принтер за допомогою різних опцій, таких як символ нового рядка для використання та чи виводити коментарі.
Метод `printer.printFile` приймає `SourceFile` і повертає рядкове представлення коду. Потім ви можете записати цей рядок у файл.
Практичне застосування API компілятора
API компілятора TypeScript має численні практичні застосування в розробці програмного забезпечення. Ось кілька прикладів:
- Лінтери: Створюйте власні лінтери для забезпечення дотримання стандартів кодування та виявлення потенційних проблем у вашому коді.
- Форматувальники коду: Створюйте форматувальники коду для автоматичного форматування вашого коду відповідно до певного стилю.
- Статичні аналізатори: Розробляйте статичні аналізатори для виявлення помилок, вразливостей безпеки та вузьких місць продуктивності у вашому коді.
- Генератори коду: Генеруйте код із шаблонів або інших вхідних даних, автоматизуючи рутинні завдання та зменшуючи шаблонний код. Наприклад, генерація клієнтів API або схем баз даних з файлу опису.
- Інструменти рефакторингу: Створюйте інструменти рефакторингу для автоматичного перейменування змінних, вилучення функцій або переміщення коду між файлами.
- Автоматизація інтернаціоналізації (i18n): Автоматично витягуйте рядки, що підлягають перекладу, з вашого коду TypeScript і генеруйте файли локалізації для різних мов. Наприклад, інструмент може сканувати код на наявність рядків, переданих у функцію `translate()`, і автоматично додавати їх до файлу ресурсів перекладу.
Приклад: Створення простого лінтера
Створімо простий лінтер, який перевіряє наявність невикористаних змінних у коді TypeScript. Цей лінтер буде ідентифікувати змінні, які оголошені, але ніколи не використовуються.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
function findUnusedVariables(sourceFile: ts.SourceFile) {
const usedVariables = new Set();
function visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
usedVariables.add(node.text);
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
const unusedVariables: string[] = [];
function checkVariableDeclaration(node: ts.Node) {
if (ts.isVariableDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
const variableName = node.name.text;
if (!usedVariables.has(variableName)) {
unusedVariables.push(variableName);
}
}
ts.forEachChild(node, checkVariableDeclaration);
}
checkVariableDeclaration(sourceFile);
return unusedVariables;
}
const unusedVariables = findUnusedVariables(sourceFile);
if (unusedVariables.length > 0) {
console.log("Unused variables:");
unusedVariables.forEach(variable => console.log(`- ${variable}`));
} else {
console.log("No unused variables found.");
}
Пояснення:
- Функція findUnusedVariables: Визначає функцію `findUnusedVariables`, яка приймає `SourceFile` як вхідні дані.
- Множина usedVariables: Створює `Set` для зберігання імен використаних змінних.
- Функція visit: Визначає рекурсивну функцію `visit`, яка обходить AST і додає імена всіх ідентифікаторів до множини `usedVariables`.
- Функція checkVariableDeclaration: Визначає рекурсивну функцію `checkVariableDeclaration`, яка перевіряє, чи є оголошення змінної невикористаним. Якщо так, вона додає ім'я змінної до масиву `unusedVariables`.
- Повернення unusedVariables: Повертає масив, що містить імена будь-яких невикористаних змінних.
- Вивід: Виводить невикористані змінні в консоль.
Цей приклад демонструє простий лінтер. Ви можете розширити його для перевірки інших стандартів кодування та виявлення інших потенційних проблем у вашому коді. Наприклад, ви можете перевіряти невикористані імпорти, надто складні функції або потенційні вразливості безпеки. Ключовим є розуміння того, як обходити AST та ідентифікувати конкретні типи вузлів, які вас цікавлять.
Найкращі практики та рекомендації
- Розумійте AST: Інвестуйте час у розуміння структури AST. Використовуйте інструменти, такі як AST explorer, для візуалізації AST вашого коду.
- Використовуйте захисники типів (Type Guards): Використовуйте захисники типів (`ts.isXXX`), щоб переконатися, що ви працюєте з правильними типами вузлів.
- Враховуйте продуктивність: Трансформації AST можуть бути обчислювально затратними. Оптимізуйте свій код, щоб мінімізувати кількість вузлів, які ви відвідуєте та трансформуєте.
- Обробляйте помилки: Грамотно обробляйте помилки. API компілятора може викидати винятки, якщо ви намагаєтеся виконати недійсні операції з AST.
- Ретельно тестуйте: Ретельно тестуйте свої трансформації, щоб переконатися, що вони дають бажані результати і не вносять нових помилок.
- Використовуйте існуючі бібліотеки: Розгляньте можливість використання існуючих бібліотек, які надають абстракції вищого рівня над API компілятора. Ці бібліотеки можуть спростити загальні завдання та зменшити кількість коду, який вам потрібно написати. Приклади включають `ts-morph` та `typescript-eslint`.
Висновок
API компілятора TypeScript — це потужний інструмент для створення передових інструментів розробки. Розуміючи, як працювати з AST, ви можете створювати лінтери, форматувальники коду, статичні аналізатори та інші інструменти, які покращують якість коду, автоматизують рутинні завдання та підвищують продуктивність розробників. Хоча API може бути складним, переваги від його освоєння значні. Цей вичерпний посібник надає основу для вивчення та ефективного використання API компілятора у ваших проєктах. Не забувайте використовувати такі інструменти, як AST Explorer, обережно поводитися з типами вузлів і ретельно тестувати свої трансформації. З практикою та наполегливістю ви зможете розкрити весь потенціал API компілятора TypeScript і створювати інноваційні рішення для світу розробки програмного забезпечення.
Для подальшого вивчення:
- Документація API компілятора TypeScript: [https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
- AST Explorer: [https://astexplorer.net/](https://astexplorer.net/)
- Бібліотека ts-morph: [https://ts-morph.com/](https://ts-morph.com/)
- typescript-eslint: [https://typescript-eslint.io/](https://typescript-eslint.io/)